package com.easyCode.sqlManager;

import com.easyCode.dataMock.DataMockManager;
import com.easyCode.sqlBuilder.SqlBuilder;
import com.easyCode.sqlBuilder.existDb.ExistDbSqlBuilder;
import com.easyCode.sqlBuilder.oop.OopSqlBuilder;
import com.easyCode.sqlBuilder.sqlInfo.BaseDataBaseSqlInfo;
import com.easyCode.sqlBuilder.sqlUtil.SqlBuildUtil;
import com.easyCode.sqlExecer.SqlExec;
import com.easyCode.sqlMapper.SqlMapper;
import com.easyCode.sqlMapper.mapInfo.DataBaseMapInfo;
import com.easyCode.sqlMapper.mapInfo.MapInfo;
import com.easyCode.sqlMapper.mapInfoAdaptor.MapInfoAdaptor;
import com.easyCode.sqlMapper.template.RenderHandler;
import com.easyCode.util.DataSourceInfoUtils;
import com.easyCode.util.StringUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * @author 大忽悠
 * @create 2022/12/1 9:57
 */
@Slf4j
@Data
public class SqlManager {
    /**
     * sql构建器,负责构建sqlInfo
     */
    private final SqlBuilder sqlBuilder;
    /**
     * sql执行器,负责执行sql语句
     */
    private final SqlExec sqlExec;
    /**
     * sql映射器,负责根据mapInfo生成相关模板代码
     */
    private final SqlMapper sqlMapper;
    /**
     * 负责将sqlInfo转换为mapInfo
     */
    private final MapInfoAdaptor<?> mapInfoAdaptor;
    /**
     * 负责生成模拟数据
     */
    private final DataMockManager dataMockManager;
    /**
     * 管理全局参数
     */
    private final GlobalProperties globalProperties;

    public SqlManager(SqlExec sqlExec, SqlMapper sqlMapper, MapInfoAdaptor<?> mapInfoAdaptor,
                      GlobalProperties globalProperties, DataSourceProperties dataSourceProperties,
                      BaseDataBaseSqlInfo baseDataBaseSqlInfo, DataMockManager dataMockManager) {
        globalProperties.propsCheck();
        if (!globalProperties.getImportFromExistTable()) {
            sqlBuilder = new OopSqlBuilder(baseDataBaseSqlInfo);
        } else {
            sqlBuilder = new ExistDbSqlBuilder(baseDataBaseSqlInfo, globalProperties.getExistTableName());
        }
        log.info("初始化sqlBuilder完毕!");

        this.sqlExec = sqlExec;
        log.info("sql执行器初始化完毕!");

        this.sqlMapper = sqlMapper;
        log.info("sqlMapper初始化完毕!");

        this.mapInfoAdaptor = mapInfoAdaptor;
        this.globalProperties = globalProperties;
        this.dataMockManager = dataMockManager;

        //设置数据库名
        sqlBuilder.getSqlInfo().setDataBaseName(DataSourceInfoUtils.getDbName(dataSourceProperties.getUrl()));
        //设置创建数据库
        sqlBuilder.getSqlInfo().setCreateDataBase(globalProperties.getCreateDataBase());
    }

    public void build() {
        //先构建,确保DataBaseSqlInfo初始化完毕
        String sql = sqlBuilder.build();
        if (!StringUtil.isNullOrEmpty(sql)) {
            buildFromNotExistDb(sql);
        } else {
            buildFromExistDb();
        }
    }

    /**
     * 跳过创建数据库和表的步骤,生成模板文件和模拟数据过程依旧
     */
    private void buildFromExistDb() {
        try {
            //阻塞直到三个任务都执行完毕--使用CompletableFuture自带的线程池执行
            CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
                if (globalProperties.getMockData()) {
                    List<String> mockData = dataMockManager.mockData();
                    sqlExec.exec(mockData.toArray(new String[0]));
                    if (globalProperties.getPrintSqlToFile()) {
                        mockData.forEach(s -> {
                            printSqlToFile(s, true);
                        });
                    }
                }
            }), CompletableFuture.runAsync(() -> {
                if (globalProperties.getSqlMapper()) {
                    doMapper();
                }
            })).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    private void buildFromNotExistDb(String sql) {
        try {
            //阻塞直到三个任务都执行完毕--使用CompletableFuture自带的线程池执行
            CompletableFuture.allOf(CompletableFuture.runAsync(() -> {
                if (globalProperties.getExecSql()&&!sqlBuilder.getSqlInfo().getTableSqlInfoList().isEmpty()) {
                    doExec(sql);
                }
            }).thenRun(() -> {
                if (globalProperties.getPrintSqlToFile()) {
                    printSqlToFile(sql, false);
                }
            }).thenRun(() -> {
                if (globalProperties.getMockData()) {
                    List<String> mockData = dataMockManager.mockData();
                    sqlExec.exec(mockData.toArray(new String[0]));
                    if (globalProperties.getPrintSqlToFile()) {
                        mockData.forEach(s -> {
                            printSqlToFile(s, true);
                        });
                    }
                }
            }), CompletableFuture.runAsync(() -> {
                if (globalProperties.getSqlMapper()) {
                    doMapper();
                }
            })).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    public void addTemplate(String templateFileName, RenderHandler renderHandler) {
        sqlMapper.addTemplate(templateFileName, renderHandler);
    }

    public void removeTemplate(String templateFileName) {
        sqlMapper.removeTemplate(templateFileName);
    }

    public void addDefaultTemplate(String templateFileName, RenderHandler renderHandler){
        sqlMapper.addDefaultTemplate(templateFileName,renderHandler);
    }

    public void removeDefaultTemplate(String templateFileName){
        sqlMapper.removeDefaultTemplate(templateFileName);
    }

    private void printSqlToFile(String sql, Boolean append) {
        String sqlFile = null;
        try {
            sqlFile = StringUtil.isNullOrEmpty(globalProperties.getSqlFilePath()) ? sqlBuilder.getSqlInfo().getDataBaseName() + ".sql" : globalProperties.getSqlFilePath();
            PrintStream printStream = new PrintStream(new FileOutputStream(sqlFile, append));
            printStream.println(SqlBuildUtil.sqlFormat(sql));
            printStream.println();
        } catch (FileNotFoundException e) {
            log.info("准备将sql写入文件时,出现{}文件找不到的错误", sqlFile);
        }
    }

    private void doMapper() {
        //需要准备一个适配器,将sqlBuilder内部掌握的sqlInfo转换为sqlMapper需要的mapInfo
        BaseDataBaseSqlInfo getSqlInfo = sqlBuilder.getSqlInfo();
        if (!mapInfoAdaptor.support(getSqlInfo)) {
            throw new IllegalArgumentException("当前mapInfoAdaptor类型为: " + mapInfoAdaptor.getClass() + " 不支持适配类型为: " + getSqlInfo.getClass() + "的sqlInfo");
        }
        MapInfo mapInfo = mapInfoAdaptor.adapter(getSqlInfo);
        if (!(mapInfo instanceof DataBaseMapInfo)) {
            throw new IllegalArgumentException("mapInfoAdaptor转换BaseDataBaseSqlInfo得到的不是BaseDataBaseMapInfo类型!");
        }
        sqlMapper.exec(((DataBaseMapInfo) mapInfo));
    }

    private void doExec(String rowSql) {
        log.info("sqlBuilder构建生成的sql语句为: \r\n {}", SqlBuildUtil.sqlFormat(rowSql));
        sqlExec.exec(rowSql.split(";"));
    }

    public void addMappedObject(Object... obj) {
        if (sqlBuilder instanceof OopSqlBuilder) {
            ((OopSqlBuilder) this.sqlBuilder).addMapperObject(obj);
        }
    }

    public void addMockObject(Object... obj) {
        this.dataMockManager.addMockObjects(obj);
    }
}
